package com.ruyuan.rapid.core.balance;

import com.ruyuan.rapid.common.config.ServiceInstance;
import com.ruyuan.rapid.common.util.TimeUtil;
import com.ruyuan.rapid.core.context.AttributeKey;
import com.ruyuan.rapid.core.context.RapidContext;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

/**
 * 负载均衡抽象类,所有负载均衡策略都需要继承该类
 * @author mac
 */
public abstract class AbstractLoadBalance implements LoadBalance {

    @Override
    public ServiceInstance select(RapidContext context) {
        Set<ServiceInstance> matchInstances = context.getAttribute(AttributeKey.MATCH_INSTANCES);
        if (matchInstances == null || matchInstances.size() == 0) {
            return null;
        }

        List<ServiceInstance> instances = new ArrayList<>(matchInstances);
        if (instances.size() == 1) {
            return instances.get(0);
        }

        ServiceInstance serviceInstance = doSelect(context, instances);
        context.putAttribute(AttributeKey.LOAD_INSTANCE, serviceInstance);
        return serviceInstance;
    }

    /**
     * 由子类实现
     * @param context
     * @param instances
     * @return
     */
    protected abstract ServiceInstance doSelect(RapidContext context, List<ServiceInstance> instances);

    protected static int getWeight(ServiceInstance instance) {
        int weight = instance.getWeight() == null ? LoadBalance.DEFAULT_WEIGHT : instance.getWeight();
        if (weight > 0) {
            // 服务启动注册的时间
            long timestamp = instance.getRegisterTime();
            if (timestamp > 0L) {
                // 服务启动了多久: 当前时间-注册时间
                int upTime = (int) (TimeUtil.currentTimeMillis() - timestamp);
                // 默认的预热时间
                int warmup = LoadBalance.DEFAULT_WARMUP;
                if (upTime > 0 && upTime < warmup) {
                    weight = calculateWarmupWeight(upTime, warmup, weight);
                }
            }
        }
        return weight;
    }

    /**
     * 计算服务在预热时间内的新权重
     * @param upTime
     * @param warmup
     * @param weight
     * @return
     */
    private static int calculateWarmupWeight(int upTime, int warmup, int weight) {
        // upTime = 2 * 60 * 1000
        // warmup = 5 * 60 * 1000
        // weight = 100
        int ww = (int) ((float) upTime / ((float) warmup / (float) weight));
        return ww < 1 ? 1 : (Math.min(ww, weight));
    }
}
